数据结构之旅(2) - 栈 - Java语言描述

栈的定义

栈(stack)是一个特殊的表,它限制了插入和删除只能在一个位置上进行,该位置是表的末端,叫作栈的顶(top)。根据操作的特性,栈也可被称为后进先出(Last In First Out,LIFO)表。对栈的基本操作有push(进栈)pop(出栈),前者相当于插入,后者则是删除 最后 插入的元素。在一个栈中,唯一可见的元素就是栈顶的元素。

stack

栈的实现

因为栈是一个特殊的表,所以任何实现表的方式都可以用来实现栈。因为栈操作是常数时间的操作,所以除非在非常独特的环境下,栈操作是不可能有明显改进的。在此,我们给出两种常见的实现方式:链式结构和数组。由于实现逻辑比较简单,所以不给出具体代码实现,大家可以自行实现。

栈的链表实现

栈的第一种实现方式是使用单链表。通过在表的顶端插入来实现 push,通过删除表顶端元素来实现 poptop 操作直考察栈顶元素并返回它的值。

其实,修改一下上一讲有关表的链式实现即可完成栈操作:在表末端进行 insert 操作即是实现 push 操作;在表末端进行 remove 操作即是实现 pop 操作;在表末端进行 get 操作即是实现 top 操作。这同时又从反面印证了有关栈的定义。

具体实现大家可以亲自动手试试。

栈的数组实现

用数组来实现栈避免了链,而且可能是更加流行的实现方案。用数组来实现栈,要注意数组的容量、要注意栈顶元素的位置。用数组来实现栈,push 操作和 pop 操作都转化为了简单的赋值操作,而 top 操作则更加简单,就是对于数组元素的取值。

具体实现大家可以亲自动手试试。

栈的应用

  1. 平衡符号

    写代码时,编译器检查程序的语法错误,例如 () 没有匹配或 {} 没有匹配等等。对于这种需求,我们可以用栈来实现。

    假设现在有一组符号 ···{ ( } )···,我们逐一将其 push 进栈,当且仅当旧栈顶元素与新栈顶元素匹配时才 pop 这两个元素,也就是说当栈内元素为 & * ^ % ( 时,接下来 push()) 就会 pop())pop((),即弹出“栈顶”的两个元素。这样一来,如果所有的符号都是匹配的,push 最后一个符号时,栈会进行 pop 操作,使得整个栈为空!

  2. 后缀表达式

    假设你现在出去购物,碰巧遇到商场打折:所有的商品均在原价的基础上打88折。在结账清算商品总价时,正确的步骤应该是:计算出商品原总价然后用总价乘0.88即可得到折后总价(或对于每件商品,用其价格乘 0.88之后再相加)。因为几乎所有的计算器都知道乘法的优先级大于加法,所以如果在你输入 10 + 20 + 30 + 40 * 0.88 时,它会输出 95.2 而不是我们想要的 88。如果该计算器使用栈来实现的,那就不会出现这样的情况了,具体原因和运算过程大家可以想一想。

    或者换个情况:只有部分商品才能参与优惠活动。使用科学计算器时我们可以通过加括号的方法得到正确的答案,而一般的简单计算器不包含括号,只支持简单的四则运算!这时候就可以用 + 逆波兰式(reverse Polish)来完成相关需求:在进行运算时若遇到一个运算符号,就在该运算符号所用于栈顶弹出的的两个元素上,并将运算结果压入栈中。例如一个后缀式:1 2 + 3 *,运算结果是 9。后缀式可以摈弃一切的优先级。

  3. 中缀到后缀的转换

    栈不仅可以运算后缀式,也可以把一个标准形式的表达式转化为后缀式,即中缀到后缀的转换。此处篇幅较长,就不仔细介绍了,感兴趣的可以好好利用搜索引擎。

一些小练习(顺序随机)

  1. 输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。
  2. 对于一个链表,请设计一个时间复杂度为O(n),额外空间复杂度为O(1)的算法,判断其是否为回文结构。
  3. 写一段代码,判断一个包括’{‘,’[‘,’(‘,’)’,’]’,’}’的表达式是否合法。